package agents;


import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import selectors.StrategySelector;
import strategies.Strategy;
import agents.AverageUtility.UtilityComparator;
import base.Environment;

import static base.Environment.Configurations.*;


/**
 * A generic agent.
 */
public abstract class Agent implements Comparable<Agent> {
	
	private static int nextId = 0;
	
	protected int id;
	protected double quota;
	protected int utilityHistorySize;
	protected double utility;
	protected Environment env;
	protected StrategySelector selector;
	protected LinkedHashMap<Strategy, AverageUtility> strategies;
	protected StrategyHistory hist;
	protected LinkedList<Double> recentUtilities;
	protected double recentSum;
	protected UAP roundUAP;
	
	
	public Agent(int quota, Environment env,
			     StrategySelector selector, Strategy... strategies) {
		this(nextId++, quota, env, selector, strategies);
	}
	
	public Agent(int id, int quota, Environment env,
				 StrategySelector selector, Strategy... strategies) {
		
		this.id                 = id;
		this.quota              = (double) quota;
		this.utilityHistorySize = env.getInt(UTILITY_HISTORY_SIZE);
		this.utility            = 0d;
		this.env                = env;
		this.selector           = selector;
		this.strategies         = new LinkedHashMap<Strategy, AverageUtility>();
		this.hist               = new StrategyHistory(env.getInt(AGENT_MEMORY)); 
		this.recentUtilities    = new LinkedList<Double>();
		this.recentSum          = 0d;
		
		env.registerAgent(this);
		for (Strategy s : strategies)
			this.strategies.put(s, new AverageUtility());
	}
	
	public UAP getUAP(){
		return roundUAP;
	}
	public int getQuota() {
		return (int) quota;
	}
	
	public Map<Strategy, AverageUtility> getStrategies() {
		return strategies;
	}
	
	public void clearRecords() {
		for (Entry<Strategy, AverageUtility> entry : strategies.entrySet()) {
			entry.getKey().clearUtilities();
			entry.getValue().clear();
		}
	}
	
	public void clearUtility() {
		utility = 0;
	}
	
	
	/**
	 * Harvests resources from the environment.
	 * <p>
	 * The utility is not a percentage, but actually a value between QUOTA (the maximum possible
	 * utility) and -QUOTA (minimum possible utility which occurs when trying to harvest the
	 * entire quota from a depleted plot).
	 * 
	 * @return the harvested utility  
	 */
	public double harvest() {
		
		Strategy strategy = selector.selectStrategy(this);
		UAP uap = strategy.harvest(getQuota(), env);
		double u = uap.utility;
		
		roundUAP = uap;
		recentUtilities.addLast(u);
		recentSum += u;
		if (recentUtilities.size() > utilityHistorySize)
			recentSum -= recentUtilities.removeFirst();
		utility += u;
		strategies.get(strategy).increaseUtility(u);
		hist.put(strategy);
		
		return u;
	}
	
	public double getQuotaFulfillment() {
		if (recentUtilities.isEmpty()) {
			return 0D;
		}
		return recentSum / (quota * recentUtilities.size());
	}
	
	/**
	 * Updates this agent's quota based on its performance and its implementation.
	 */
	public abstract void updateQuota();
	
	/**
	 * Updates this agent's strategies' harvesting targets according to its implementation.
	 */
	public abstract void updateStrategiesTargets();
	
	/**
	 * Updates this agent's strategies' harvesting paths according to its implementation.
	 */
	public abstract void updateStrategiesPaths();
	
	public Strategy getLastPlayedStrategy(){
		int lstIndex = hist.history.size() - 1;
		return hist.history.get(lstIndex);
	}
	
	public LinkedList<Entry<Strategy, AverageUtility>>
		getSortedStrategies(UtilityComparator comparator) {
		
		LinkedList<Entry<Strategy, AverageUtility>> sorted =
				new LinkedList<Entry<Strategy, AverageUtility>>(getStrategies().entrySet());
		Collections.sort(sorted, comparator);
		return sorted;
	}
	
	public void displayTopStrategy(){
		StringBuilder sb = new StringBuilder("@@ ").append(getClass().getSimpleName()).append(" #")
									.append(id).append("- top Strategy: ").append(hist.toString())
									.append("\n");
		
		System.out.println(sb.toString());
	}
	
	public void pPrint() {
		
		StringBuilder sb = new StringBuilder(toString()).append("\n");
		
		for (Entry<Strategy, AverageUtility> entry : strategies.entrySet())
			sb.append(entry.getKey()).append(" - ").append(entry.getValue().getSteps())
			  .append(" @ ").append(entry.getValue().getAverageUtility()).append("\n");
		
		System.out.print(sb.toString());
	}
	
	@Override
	public String toString() {
		return new StringBuilder("@@ ")
				.append(getClass().getSimpleName()).append(" #").append(id)
				.append(": utility=").append(utility).append("; quota=").append((int) quota)
				.toString();
	}
	
	@Override
	public int compareTo(Agent agent) {
		return id - agent.id;
	}
	
	public int getID(){
		return id;		
	}
}
